<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Controller
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: Front.php 23775 2011-03-01 17:25:24Z ralph $
 */

/** Zend_Loader */
require_once 'Zend/Loader.php';

/** Zend_Controller_Action_HelperBroker */
require_once 'Zend/Controller/Action/HelperBroker.php';

/** Zend_Controller_Plugin_Broker */
require_once 'Zend/Controller/Plugin/Broker.php';

/**
 * @category   Zend
 * @package    Zend_Controller
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Controller_Front {
	/**
	 * Base URL
	 * @var string
	 */
	protected $_baseUrl = null;
	
	/**
	 * Directory|ies where controllers are stored
	 *
	 * @var string|array
	 */
	protected $_controllerDir = null;
	
	/**
	 * Instance of Zend_Controller_Dispatcher_Interface
	 * @var Zend_Controller_Dispatcher_Interface
	 */
	protected $_dispatcher = null;
	
	/**
	 * Singleton instance
	 *
	 * Marked only as protected to allow extension of the class. To extend,
	 * simply override {@link getInstance()}.
	 *
	 * @var Zend_Controller_Front
	 */
	protected static $_instance = null;
	
	/**
	 * Array of invocation parameters to use when instantiating action
	 * controllers
	 * @var array
	 */
	protected $_invokeParams = array ();
	
	/**
	 * Subdirectory within a module containing controllers; defaults to 'controllers'
	 * @var string
	 */
	protected $_moduleControllerDirectoryName = 'controllers';
	
	/**
	 * Instance of Zend_Controller_Plugin_Broker
	 * @var Zend_Controller_Plugin_Broker
	 */
	protected $_plugins = null;
	
	/**
	 * Instance of Zend_Controller_Request_Abstract
	 * @var Zend_Controller_Request_Abstract
	 */
	protected $_request = null;
	
	/**
	 * Instance of Zend_Controller_Response_Abstract
	 * @var Zend_Controller_Response_Abstract
	 */
	protected $_response = null;
	
	/**
	 * Whether or not to return the response prior to rendering output while in
	 * {@link dispatch()}; default is to send headers and render output.
	 * @var boolean
	 */
	protected $_returnResponse = false;
	
	/**
	 * Instance of Zend_Controller_Router_Interface
	 * @var Zend_Controller_Router_Interface
	 */
	protected $_router = null;
	
	/**
	 * Whether or not exceptions encountered in {@link dispatch()} should be
	 * thrown or trapped in the response object
	 * @var boolean
	 */
	protected $_throwExceptions = false;
	
	/**
	 * Constructor
	 *
	 * Instantiate using {@link getInstance()}; front controller is a singleton
	 * object.
	 *
	 * Instantiates the plugin broker.
	 *
	 * @return void
	 */
	protected function __construct() {
		$this->_plugins = new Zend_Controller_Plugin_Broker ();
	}
	
	/**
	 * Enforce singleton; disallow cloning
	 *
	 * @return void
	 */
	private function __clone() {
	}
	
	/**
	 * Singleton instance
	 *
	 * @return Zend_Controller_Front
	 */
	public static function getInstance() {
		if (null === self::$_instance) {
			self::$_instance = new self ();
		}
		
		return self::$_instance;
	}
	
	/**
	 * Resets all object properties of the singleton instance
	 *
	 * Primarily used for testing; could be used to chain front controllers.
	 *
	 * Also resets action helper broker, clearing all registered helpers.
	 *
	 * @return void
	 */
	public function resetInstance() {
		$reflection = new ReflectionObject ( $this );
		foreach ( $reflection->getProperties () as $property ) {
			$name = $property->getName ();
			switch ($name) {
				case '_instance' :
					break;
				case '_controllerDir' :
				case '_invokeParams' :
					$this->{$name} = array ();
					break;
				case '_plugins' :
					$this->{$name} = new Zend_Controller_Plugin_Broker ();
					break;
				case '_throwExceptions' :
				case '_returnResponse' :
					$this->{$name} = false;
					break;
				case '_moduleControllerDirectoryName' :
					$this->{$name} = 'controllers';
					break;
				default :
					$this->{$name} = null;
					break;
			}
		}
		Zend_Controller_Action_HelperBroker::resetHelpers ();
	}
	
	/**
	 * Convenience feature, calls setControllerDirectory()->setRouter()->dispatch()
	 *
	 * In PHP 5.1.x, a call to a static method never populates $this -- so run()
	 * may actually be called after setting up your front controller.
	 *
	 * @param string|array $controllerDirectory Path to Zend_Controller_Action
	 * controller classes or array of such paths
	 * @return void
	 * @throws Zend_Controller_Exception if called from an object instance
	 */
	public static function run($controllerDirectory) {
		self::getInstance ()->setControllerDirectory ( $controllerDirectory )->dispatch ();
	}
	
	/**
	 * Add a controller directory to the controller directory stack
	 *
	 * If $args is presented and is a string, uses it for the array key mapping
	 * to the directory specified.
	 *
	 * @param string $directory
	 * @param string $module Optional argument; module with which to associate directory. If none provided, assumes 'default'
	 * @return Zend_Controller_Front
	 * @throws Zend_Controller_Exception if directory not found or readable
	 */
	public function addControllerDirectory($directory, $module = null) {
		$this->getDispatcher ()->addControllerDirectory ( $directory, $module );
		return $this;
	}
	
	/**
	 * Set controller directory
	 *
	 * Stores controller directory(ies) in dispatcher. May be an array of
	 * directories or a string containing a single directory.
	 *
	 * @param string|array $directory Path to Zend_Controller_Action controller
	 * classes or array of such paths
	 * @param  string $module Optional module name to use with string $directory
	 * @return Zend_Controller_Front
	 */
	public function setControllerDirectory($directory, $module = null) {
		$this->getDispatcher ()->setControllerDirectory ( $directory, $module );
		return $this;
	}
	
	/**
	 * Retrieve controller directory
	 *
	 * Retrieves:
	 * - Array of all controller directories if no $name passed
	 * - String path if $name passed and exists as a key in controller directory array
	 * - null if $name passed but does not exist in controller directory keys
	 *
	 * @param  string $name Default null
	 * @return array|string|null
	 */
	public function getControllerDirectory($name = null) {
		return $this->getDispatcher ()->getControllerDirectory ( $name );
	}
	
	/**
	 * Remove a controller directory by module name
	 *
	 * @param  string $module
	 * @return bool
	 */
	public function removeControllerDirectory($module) {
		return $this->getDispatcher ()->removeControllerDirectory ( $module );
	}
	
	/**
	 * Specify a directory as containing modules
	 *
	 * Iterates through the directory, adding any subdirectories as modules;
	 * the subdirectory within each module named after {@link $_moduleControllerDirectoryName}
	 * will be used as the controller directory path.
	 *
	 * @param  string $path
	 * @return Zend_Controller_Front
	 */
	public function addModuleDirectory($path) {
		try {
			$dir = new DirectoryIterator ( $path );
		} catch ( Exception $e ) {
			require_once 'Zend/Controller/Exception.php';
			throw new Zend_Controller_Exception ( "Directory $path not readable", 0, $e );
		}
		foreach ( $dir as $file ) {
			if ($file->isDot () || ! $file->isDir ()) {
				continue;
			}
			
			$module = $file->getFilename ();
			
			// Don't use SCCS directories as modules
			if (preg_match ( '/^[^a-z]/i', $module ) || ('CVS' == $module)) {
				continue;
			}
			
			$moduleDir = $file->getPathname () . DIRECTORY_SEPARATOR . $this->getModuleControllerDirectoryName ();
			$this->addControllerDirectory ( $moduleDir, $module );
		}
		
		return $this;
	}
	
	/**
	 * Return the path to a module directory (but not the controllers directory within)
	 *
	 * @param  string $module
	 * @return string|null
	 */
	public function getModuleDirectory($module = null) {
		if (null === $module) {
			$request = $this->getRequest ();
			if (null !== $request) {
				$module = $this->getRequest ()->getModuleName ();
			}
			if (empty ( $module )) {
				$module = $this->getDispatcher ()->getDefaultModule ();
			}
		}
		
		$controllerDir = $this->getControllerDirectory ( $module );
		
		if ((null === $controllerDir) || ! is_string ( $controllerDir )) {
			return null;
		}
		
		return dirname ( $controllerDir );
	}
	
	/**
	 * Set the directory name within a module containing controllers
	 *
	 * @param  string $name
	 * @return Zend_Controller_Front
	 */
	public function setModuleControllerDirectoryName($name = 'controllers') {
		$this->_moduleControllerDirectoryName = ( string ) $name;
		
		return $this;
	}
	
	/**
	 * Return the directory name within a module containing controllers
	 *
	 * @return string
	 */
	public function getModuleControllerDirectoryName() {
		return $this->_moduleControllerDirectoryName;
	}
	
	/**
	 * Set the default controller (unformatted string)
	 *
	 * @param string $controller
	 * @return Zend_Controller_Front
	 */
	public function setDefaultControllerName($controller) {
		$dispatcher = $this->getDispatcher ();
		$dispatcher->setDefaultControllerName ( $controller );
		return $this;
	}
	
	/**
	 * Retrieve the default controller (unformatted string)
	 *
	 * @return string
	 */
	public function getDefaultControllerName() {
		return $this->getDispatcher ()->getDefaultControllerName ();
	}
	
	/**
	 * Set the default action (unformatted string)
	 *
	 * @param string $action
	 * @return Zend_Controller_Front
	 */
	public function setDefaultAction($action) {
		$dispatcher = $this->getDispatcher ();
		$dispatcher->setDefaultAction ( $action );
		return $this;
	}
	
	/**
	 * Retrieve the default action (unformatted string)
	 *
	 * @return string
	 */
	public function getDefaultAction() {
		return $this->getDispatcher ()->getDefaultAction ();
	}
	
	/**
	 * Set the default module name
	 *
	 * @param string $module
	 * @return Zend_Controller_Front
	 */
	public function setDefaultModule($module) {
		$dispatcher = $this->getDispatcher ();
		$dispatcher->setDefaultModule ( $module );
		return $this;
	}
	
	/**
	 * Retrieve the default module
	 *
	 * @return string
	 */
	public function getDefaultModule() {
		return $this->getDispatcher ()->getDefaultModule ();
	}
	
	/**
	 * Set request class/object
	 *
	 * Set the request object.  The request holds the request environment.
	 *
	 * If a class name is provided, it will instantiate it
	 *
	 * @param string|Zend_Controller_Request_Abstract $request
	 * @throws Zend_Controller_Exception if invalid request class
	 * @return Zend_Controller_Front
	 */
	public function setRequest($request) {
		if (is_string ( $request )) {
			if (! class_exists ( $request )) {
				require_once 'Zend/Loader.php';
				Zend_Loader::loadClass ( $request );
			}
			$request = new $request ();
		}
		if (! $request instanceof Zend_Controller_Request_Abstract) {
			require_once 'Zend/Controller/Exception.php';
			throw new Zend_Controller_Exception ( 'Invalid request class' );
		}
		
		$this->_request = $request;
		
		return $this;
	}
	
	/**
	 * Return the request object.
	 *
	 * @return null|Zend_Controller_Request_Abstract
	 */
	public function getRequest() {
		return $this->_request;
	}
	
	/**
	 * Set router class/object
	 *
	 * Set the router object.  The router is responsible for mapping
	 * the request to a controller and action.
	 *
	 * If a class name is provided, instantiates router with any parameters
	 * registered via {@link setParam()} or {@link setParams()}.
	 *
	 * @param string|Zend_Controller_Router_Interface $router
	 * @throws Zend_Controller_Exception if invalid router class
	 * @return Zend_Controller_Front
	 */
	public function setRouter($router) {
		if (is_string ( $router )) {
			if (! class_exists ( $router )) {
				require_once 'Zend/Loader.php';
				Zend_Loader::loadClass ( $router );
			}
			$router = new $router ();
		}
		
		if (! $router instanceof Zend_Controller_Router_Interface) {
			require_once 'Zend/Controller/Exception.php';
			throw new Zend_Controller_Exception ( 'Invalid router class' );
		}
		
		$router->setFrontController ( $this );
		$this->_router = $router;
		
		return $this;
	}
	
	/**
	 * Return the router object.
	 *
	 * Instantiates a Zend_Controller_Router_Rewrite object if no router currently set.
	 *
	 * @return Zend_Controller_Router_Interface
	 */
	public function getRouter() {
		if (null == $this->_router) {
			require_once 'Zend/Controller/Router/Rewrite.php';
			$this->setRouter ( new Zend_Controller_Router_Rewrite () );
		}
		
		return $this->_router;
	}
	
	/**
	 * Set the base URL used for requests
	 *
	 * Use to set the base URL segment of the REQUEST_URI to use when
	 * determining PATH_INFO, etc. Examples:
	 * - /admin
	 * - /myapp
	 * - /subdir/index.php
	 *
	 * Note that the URL should not include the full URI. Do not use:
	 * - http://example.com/admin
	 * - http://example.com/myapp
	 * - http://example.com/subdir/index.php
	 *
	 * If a null value is passed, this can be used as well for autodiscovery (default).
	 *
	 * @param string $base
	 * @return Zend_Controller_Front
	 * @throws Zend_Controller_Exception for non-string $base
	 */
	public function setBaseUrl($base = null) {
		if (! is_string ( $base ) && (null !== $base)) {
			require_once 'Zend/Controller/Exception.php';
			throw new Zend_Controller_Exception ( 'Rewrite base must be a string' );
		}
		
		$this->_baseUrl = $base;
		
		if ((null !== ($request = $this->getRequest ())) && (method_exists ( $request, 'setBaseUrl' ))) {
			$request->setBaseUrl ( $base );
		}
		
		return $this;
	}
	
	/**
	 * Retrieve the currently set base URL
	 *
	 * @return string
	 */
	public function getBaseUrl() {
		$request = $this->getRequest ();
		if ((null !== $request) && method_exists ( $request, 'getBaseUrl' )) {
			return $request->getBaseUrl ();
		}
		
		return $this->_baseUrl;
	}
	
	/**
	 * Set the dispatcher object.  The dispatcher is responsible for
	 * taking a Zend_Controller_Dispatcher_Token object, instantiating the controller, and
	 * call the action method of the controller.
	 *
	 * @param Zend_Controller_Dispatcher_Interface $dispatcher
	 * @return Zend_Controller_Front
	 */
	public function setDispatcher(Zend_Controller_Dispatcher_Interface $dispatcher) {
		$this->_dispatcher = $dispatcher;
		return $this;
	}
	
	/**
	 * Return the dispatcher object.
	 *
	 * @return Zend_Controller_Dispatcher_Interface
	 */
	public function getDispatcher() {
		/**
		 * Instantiate the default dispatcher if one was not set.
		 */
		if (! $this->_dispatcher instanceof Zend_Controller_Dispatcher_Interface) {
			require_once 'Zend/Controller/Dispatcher/Standard.php';
			$this->_dispatcher = new Zend_Controller_Dispatcher_Standard ();
		}
		return $this->_dispatcher;
	}
	
	/**
	 * Set response class/object
	 *
	 * Set the response object.  The response is a container for action
	 * responses and headers. Usage is optional.
	 *
	 * If a class name is provided, instantiates a response object.
	 *
	 * @param string|Zend_Controller_Response_Abstract $response
	 * @throws Zend_Controller_Exception if invalid response class
	 * @return Zend_Controller_Front
	 */
	public function setResponse($response) {
		if (is_string ( $response )) {
			if (! class_exists ( $response )) {
				require_once 'Zend/Loader.php';
				Zend_Loader::loadClass ( $response );
			}
			$response = new $response ();
		}
		if (! $response instanceof Zend_Controller_Response_Abstract) {
			require_once 'Zend/Controller/Exception.php';
			throw new Zend_Controller_Exception ( 'Invalid response class' );
		}
		
		$this->_response = $response;
		
		return $this;
	}
	
	/**
	 * Return the response object.
	 *
	 * @return null|Zend_Controller_Response_Abstract
	 */
	public function getResponse() {
		return $this->_response;
	}
	
	/**
	 * Add or modify a parameter to use when instantiating an action controller
	 *
	 * @param string $name
	 * @param mixed $value
	 * @return Zend_Controller_Front
	 */
	public function setParam($name, $value) {
		$name = ( string ) $name;
		$this->_invokeParams [$name] = $value;
		return $this;
	}
	
	/**
	 * Set parameters to pass to action controller constructors
	 *
	 * @param array $params
	 * @return Zend_Controller_Front
	 */
	public function setParams(array $params) {
		$this->_invokeParams = array_merge ( $this->_invokeParams, $params );
		return $this;
	}
	
	/**
	 * Retrieve a single parameter from the controller parameter stack
	 *
	 * @param string $name
	 * @return mixed
	 */
	public function getParam($name) {
		if (isset ( $this->_invokeParams [$name] )) {
			return $this->_invokeParams [$name];
		}
		
		return null;
	}
	
	/**
	 * Retrieve action controller instantiation parameters
	 *
	 * @return array
	 */
	public function getParams() {
		return $this->_invokeParams;
	}
	
	/**
	 * Clear the controller parameter stack
	 *
	 * By default, clears all parameters. If a parameter name is given, clears
	 * only that parameter; if an array of parameter names is provided, clears
	 * each.
	 *
	 * @param null|string|array single key or array of keys for params to clear
	 * @return Zend_Controller_Front
	 */
	public function clearParams($name = null) {
		if (null === $name) {
			$this->_invokeParams = array ();
		} elseif (is_string ( $name ) && isset ( $this->_invokeParams [$name] )) {
			unset ( $this->_invokeParams [$name] );
		} elseif (is_array ( $name )) {
			foreach ( $name as $key ) {
				if (is_string ( $key ) && isset ( $this->_invokeParams [$key] )) {
					unset ( $this->_invokeParams [$key] );
				}
			}
		}
		
		return $this;
	}
	
	/**
	 * Register a plugin.
	 *
	 * @param  Zend_Controller_Plugin_Abstract $plugin
	 * @param  int $stackIndex Optional; stack index for plugin
	 * @return Zend_Controller_Front
	 */
	public function registerPlugin(Zend_Controller_Plugin_Abstract $plugin, $stackIndex = null) {
		$this->_plugins->registerPlugin ( $plugin, $stackIndex );
		return $this;
	}
	
	/**
	 * Unregister a plugin.
	 *
	 * @param  string|Zend_Controller_Plugin_Abstract $plugin Plugin class or object to unregister
	 * @return Zend_Controller_Front
	 */
	public function unregisterPlugin($plugin) {
		$this->_plugins->unregisterPlugin ( $plugin );
		return $this;
	}
	
	/**
	 * Is a particular plugin registered?
	 *
	 * @param  string $class
	 * @return bool
	 */
	public function hasPlugin($class) {
		return $this->_plugins->hasPlugin ( $class );
	}
	
	/**
	 * Retrieve a plugin or plugins by class
	 *
	 * @param  string $class
	 * @return false|Zend_Controller_Plugin_Abstract|array
	 */
	public function getPlugin($class) {
		return $this->_plugins->getPlugin ( $class );
	}
	
	/**
	 * Retrieve all plugins
	 *
	 * @return array
	 */
	public function getPlugins() {
		return $this->_plugins->getPlugins ();
	}
	
	/**
	 * Set the throwExceptions flag and retrieve current status
	 *
	 * Set whether exceptions encounted in the dispatch loop should be thrown
	 * or caught and trapped in the response object.
	 *
	 * Default behaviour is to trap them in the response object; call this
	 * method to have them thrown.
	 *
	 * Passing no value will return the current value of the flag; passing a
	 * boolean true or false value will set the flag and return the current
	 * object instance.
	 *
	 * @param boolean $flag Defaults to null (return flag state)
	 * @return boolean|Zend_Controller_Front Used as a setter, returns object; as a getter, returns boolean
	 */
	public function throwExceptions($flag = null) {
		if ($flag !== null) {
			$this->_throwExceptions = ( bool ) $flag;
			return $this;
		}
		
		return $this->_throwExceptions;
	}
	
	/**
	 * Set whether {@link dispatch()} should return the response without first
	 * rendering output. By default, output is rendered and dispatch() returns
	 * nothing.
	 *
	 * @param boolean $flag
	 * @return boolean|Zend_Controller_Front Used as a setter, returns object; as a getter, returns boolean
	 */
	public function returnResponse($flag = null) {
		if (true === $flag) {
			$this->_returnResponse = true;
			return $this;
		} elseif (false === $flag) {
			$this->_returnResponse = false;
			return $this;
		}
		
		return $this->_returnResponse;
	}
	
	/**
	 * Dispatch an HTTP request to a controller/action.
	 *
	 * @param Zend_Controller_Request_Abstract|null $request
	 * @param Zend_Controller_Response_Abstract|null $response
	 * @return void|Zend_Controller_Response_Abstract Returns response object if returnResponse() is true
	 */
	public function dispatch(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null) {
		if (! $this->getParam ( 'noErrorHandler' ) && ! $this->_plugins->hasPlugin ( 'Zend_Controller_Plugin_ErrorHandler' )) {
			// Register with stack index of 100
			require_once 'Zend/Controller/Plugin/ErrorHandler.php';
			$this->_plugins->registerPlugin ( new Zend_Controller_Plugin_ErrorHandler (), 100 );
		}
		
		if (! $this->getParam ( 'noViewRenderer' ) && ! Zend_Controller_Action_HelperBroker::hasHelper ( 'viewRenderer' )) {
			require_once 'Zend/Controller/Action/Helper/ViewRenderer.php';
			Zend_Controller_Action_HelperBroker::getStack ()->offsetSet ( - 80, new Zend_Controller_Action_Helper_ViewRenderer () );
		}
		
		/**
		 * Instantiate default request object (HTTP version) if none provided
		 */
		if (null !== $request) {
			$this->setRequest ( $request );
		} elseif ((null === $request) && (null === ($request = $this->getRequest ()))) {
			require_once 'Zend/Controller/Request/Http.php';
			$request = new Zend_Controller_Request_Http ();
			$this->setRequest ( $request );
		}
		
		/**
		 * Set base URL of request object, if available
		 */
		if (is_callable ( array ($this->_request, 'setBaseUrl' ) )) {
			if (null !== $this->_baseUrl) {
				$this->_request->setBaseUrl ( $this->_baseUrl );
			}
		}
		
		/**
		 * Instantiate default response object (HTTP version) if none provided
		 */
		if (null !== $response) {
			$this->setResponse ( $response );
		} elseif ((null === $this->_response) && (null === ($this->_response = $this->getResponse ()))) {
			require_once 'Zend/Controller/Response/Http.php';
			$response = new Zend_Controller_Response_Http ();
			$this->setResponse ( $response );
		}
		
		/**
		 * Register request and response objects with plugin broker
		 */
		$this->_plugins->setRequest ( $this->_request )->setResponse ( $this->_response );
		
		/**
		 * Initialize router
		 */
		$router = $this->getRouter ();
		$router->setParams ( $this->getParams () );
		
		/**
		 * Initialize dispatcher
		 */
		$dispatcher = $this->getDispatcher ();
		$dispatcher->setParams ( $this->getParams () )->setResponse ( $this->_response );
		
		// Begin dispatch
		try {
			/**
			 * Route request to controller/action, if a router is provided
			 */
			
			/**
			 * Notify plugins of router startup
			 */
			$this->_plugins->routeStartup ( $this->_request );
			
			try {
				$router->route ( $this->_request );
			} catch ( Exception $e ) {
				if ($this->throwExceptions ()) {
					throw $e;
				}
				
				$this->_response->setException ( $e );
			}
			
			/**
			 * Notify plugins of router completion
			 */
			$this->_plugins->routeShutdown ( $this->_request );
			
			/**
			 * Notify plugins of dispatch loop startup
			 */
			$this->_plugins->dispatchLoopStartup ( $this->_request );
			
			/**
			 * Attempt to dispatch the controller/action. If the $this->_request
			 * indicates that it needs to be dispatched, move to the next
			 * action in the request.
			 */
			do {
				$this->_request->setDispatched ( true );
				
				/**
				 * Notify plugins of dispatch startup
				 */
				$this->_plugins->preDispatch ( $this->_request );
				
				/**
				 * Skip requested action if preDispatch() has reset it
				 */
				if (! $this->_request->isDispatched ()) {
					continue;
				}
				
				/**
				 * Dispatch request
				 */
				try {
					$dispatcher->dispatch ( $this->_request, $this->_response );
				} catch ( Exception $e ) {
					if ($this->throwExceptions ()) {
						throw $e;
					}
					$this->_response->setException ( $e );
				}
				
				/**
				 * Notify plugins of dispatch completion
				 */
				$this->_plugins->postDispatch ( $this->_request );
			} while ( ! $this->_request->isDispatched () );
		} catch ( Exception $e ) {
			if ($this->throwExceptions ()) {
				throw $e;
			}
			
			$this->_response->setException ( $e );
		}
		
		/**
		 * Notify plugins of dispatch loop completion
		 */
		try {
			$this->_plugins->dispatchLoopShutdown ();
		} catch ( Exception $e ) {
			if ($this->throwExceptions ()) {
				throw $e;
			}
			
			$this->_response->setException ( $e );
		}
		
		if ($this->returnResponse ()) {
			return $this->_response;
		}
		
		$this->_response->sendResponse ();
	}
}
